#include "gkv.hpp"
#include <zlib.h>
//#define GKV_SILENT

namespace gkv
{

// Constructor
GKV_File::GKV_File()
{
#ifndef GKV_SILENT
// Print version number
std::cout << caption.c_str() << " v" << version.c_str() << std::endl;
#endif

	// Default the header
	memset(&m_Header, 0, sizeof(header_t));

	// Add a blank dummy node to the file table
	tableentry_t* Dummy = new tableentry_t();
	memset(Dummy, 0, sizeof(tableentry_t));
	m_FileTable = Dummy;
}
// Deconstructor
GKV_File::~GKV_File()
{

}

// Private Members
char*
GKV_File::getEscapedValue(char* to_escape, int size)
{
	char* escaped = new char[size+1];
	char tmp[1];
	strcpy(tmp, "\0");

	for (int i = 0; i < size; i++)
	{
		escaped[i] = to_escape[i];
	}
	escaped[size] = tmp[0];

	return escaped;
}

// Public Members
std::string
GKV_File::generateFileList(std::string dir_path, std::list<filelist_t>& rootList, int trim, bool is_root)
{
	static std::string origin;
	static std::string filename;
	static std::string ret;

    filelist_t thisList;
    boost::filesystem::directory_iterator end_itr;
    boost::filesystem::path fspath = dir_path;
    boost::regex e("^(.*/)?(?:$|(.+?)(?:(\\.[^.]*$)|$))",boost::regex::icase|boost::regex::perl);


    if (dir_path.substr(trim-1) == "/" && is_root == true)
    {
		// Everything is fine.
    }
    else if (dir_path.substr(trim-1) != "/" && is_root == true)
    {
        dir_path = dir_path + "/";
        trim++;
    }

	////
    // This was a work around to a problem with the generated file name.
    // As you can tell, it wasn't much of a work-around, it's very error prone.
    //
    // This issue has been resolved by using a regular expression.
	//if (tmp_path == "./" || tmp_path == "../" || tmp_path == "./../" || tmp_path == "./../../")
	//{
	//	std::cout << "Please supply a named directory path; none of this dot-slash stuff." << std::endl;
	//	return "";
	//}
	////

    fspath = dir_path;
    if (!boost::filesystem::exists(fspath))
    {
        std::cout << "Tried to read a path that doesn't exist" << std::endl;
        return "";
    }
    if (is_root)
    {
        std::cout << "---------------" << std::endl;
        std::cout << "Generating List" << std::endl;
        std::cout << "---------------" << std::endl;
        if (boost::filesystem::is_empty(fspath))
        {
            std::cout << "Specified directory: " << boost::filesystem::system_complete(fspath) << " is EMPTY" << std::endl;
            return "";
        }
		std::string complete_path = boost::filesystem::system_complete(fspath).string();
		complete_path = complete_path.replace(complete_path.length()-1, complete_path.length(), "");

        filename = boost::regex_replace(complete_path,e,"$2"); // + ".gkv";

//std::cout << boost::regex_replace(complete_path,e,"$1") << std::endl;
        origin = boost::regex_replace(complete_path,e,"$1");
        origin = origin.replace(origin.length()-1, origin.length(), "");
		ret = (origin + "/" + filename).c_str();
    }

    rootList.push_back(thisList);
    std::list<filelist_t>::iterator it = rootList.end();
    it--;
    (*it).path = dir_path.substr(trim);
    for (boost::filesystem::directory_iterator itr(fspath); itr != end_itr; ++itr)
    {
        std::cout << "o";
        if ( boost::filesystem::is_directory(itr->status()) )
        {
            std::cout << "d";
            generateFileList(itr->path().string(), rootList, trim, false);
        }
        else if (boost::filesystem::is_regular_file(itr->status()))
        {
			boost::filesystem::path pathreal = ".";
			std::string realpath;
			boost::filesystem::path pathtest = itr->path().parent_path();
			std::string testpath;

			realpath = boost::filesystem::system_complete(pathreal).string() + "/" + filename + ".gkv";
			testpath = boost::filesystem::system_complete(pathtest).string() + "/" + itr->path().filename();
			//std::cout << "DEBUG: " << realpath.c_str() << std::endl;
			if (realpath == testpath)
			{
				std::cout << "DUPLICATE_GKV_REMOVED";
				boost::filesystem::remove(realpath);
			}
			else
			{
				std::cout << "f";
				(*it).theList.push_back(itr->path().filename());
			}
        }
        std::cout << "x";
    }

    if (is_root)
    {
    	std::cout << std::endl;
		std::cout << "--------------" << std::endl;
		std::cout << "List Generated" << std::endl;
		std::cout << "--------------" << std::endl;
    }


    return ret;
}

std::string // returns the name of the file created
GKV_File::create(std::string dirpath)
{
	boost::regex e("^(.*/)?(?:$|(.+?)(?:(\\.[^.]*$)|$))",boost::regex::icase|boost::regex::perl);
	// File pointer for writing to the GKV
	FILE*	GKVStream;
	// Output filename derived from working_path
	std::string working_path;
	std::string outfile;
	dword_t counter = 0;

	std::list<filelist_t> rootList;

	working_path = generateFileList(dirpath, rootList, dirpath.length());
	working_path = boost::regex_replace(working_path,e,"$0");
	outfile = boost::regex_replace(working_path,e,"$2"); // DOES NOT INCLUDE FILE EXTENSION!!!

	// Get the file count:
	std::list<filelist_t>::iterator it;
	for(it = rootList.begin(); it != rootList.end(); it++)
	{
		std::vector<std::string>::iterator its;
		for(its = (*it).theList.begin(); its != (*it).theList.end(); its++)
		{
			counter++;
		}
	}

	if (rootList.size() > 0)
	{
		// Generate the Header and File Tables
		// (File List, Working Path, Counter, Compression)
		if (!generateHFT(rootList, working_path, counter, false))
		{
			return "";
		}
		rootList.clear();

		// Work out the offsets
		if (!workOutOffsets())
		{
			return "";
		}

		std::cout << "------------" << std::endl;
		std::cout << "Working Path: " << working_path.c_str() << std::endl;
		std::cout << "------------" << std::endl;

		// Open the file stream for writing the GKV
		GKVStream = fopen((outfile+".gkv").c_str(), "wb");
		if( !GKVStream ){ return ""; }

		// Write the GKV header
		fwrite( &m_Header, sizeof(header_t), 1, GKVStream );

		// Process the File Table
		// Get a local copy of the file table head
		tableentry_t* tempEntry;
		tempEntry = m_FileTable;

		// Write out each file table entry
		while( tempEntry != 0 )
		{
			// Check for the dummy entry (break if found)
			if( tempEntry->nextFTEntry == 0 ){ break; }

			// Create a BYTE pointer for byte access to a file table entry
			byte_t Ptr[sizeof(tableentry_t)] = {0};

			// Copy the current file table entry in to the byte array
			memcpy( &Ptr, tempEntry, sizeof(tableentry_t) );

			for( unsigned int i = 0; i < sizeof(tableentry_t); i++ )
			{
				// Temporary BYTE variable
				byte_t Temp = 0;

				// Make equal to the relevant byte of the FT entry
				Temp = Ptr[i];

				////
				// Base64 Encoding goes here (posibly???)
				////

				// Write the FT encrypted BYTE value
				fwrite( &Temp, sizeof(byte_t), 1, GKVStream );
			}

			// Move on to the next file table entry
			tempEntry = tempEntry->nextFTEntry;
		}

		// Close the stream used for the header and file table
		fclose(GKVStream);


		// Reset variable to be the head of the file table
		tempEntry = m_FileTable;

		std::cout << "----------" << std::endl;
		std::cout << "Processing" << std::endl;
		std::cout << "----------" << std::endl;
		// Read in each file at a time, encrypt it using the cypher and add to GKV
		int j = 1;
		while( tempEntry != 0 )
		{
			// Declare local variables
			FILE* InputStream;
			char Buffer[300] = {0};
			byte_t dataBuffer = 0;

			boost::filesystem::path pathout = ".";
			std::string outpath = boost::filesystem::system_complete(pathout).string();

			// Check for the dummy file table entry
			if( tempEntry->nextFTEntry == 0 )
			{
				return (outfile+".gkv").c_str();
			}
			// Display which guid is being processed
			std::cout << "(" << j << "/" << m_Header.numFTEntries << ") | " << tempEntry->guid;

			// Open the GKV file for writing
			GKVStream = fopen( (outfile+".gkv").c_str(), "r+b" );
			if( !GKVStream ){ return ""; }

			// Create the absolute path of the file to be added to the GKV
			memcpy( Buffer, (working_path+"/"+tempEntry->guid).c_str(), 300 );



			// Open the data stream for reading in a file to be added to the GVK
			InputStream = fopen( Buffer, "rb" );
			if( !InputStream )
			{
				fclose( GKVStream );
				return "";
			}

			// Set the position in the GKV file for this file to be written at
			fpos_t pos;
			pos = tempEntry->offset;
			fsetpos( GKVStream, &pos );

			// Read in the file a byte at a time, encrypt it and write to GKV
			int k = 0;
			for( dword_t i = 0; i < tempEntry->fileSize; i++ )
			{
				if (k == 128000)
				{
					std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ") / " << tempEntry->guid;
					k++;
				}
				else if (k == 256000)
				{
					std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ") - " << tempEntry->guid;
					k++;
				}
				else if (k == 384000)
				{
					std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ") \\ " << tempEntry->guid;
					k++;
				}
				else if (k == 512000)
				{
					std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ") | " << tempEntry->guid;
					k = 0;
				} else {
					k++;
				}


				// Get the first BYTE from the file to be added
				dataBuffer = fgetc( InputStream );

				////
				// Encrypt the file accordingly
				////

				// Write to the GKV file starting at the offset
				fwrite( &dataBuffer, sizeof(byte_t), 1, GKVStream );
			}
			std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ")   " << tempEntry->guid;
			std::cout << std::endl;

			// Close both the file streams
			fclose( InputStream );
			fclose( GKVStream );

			// Advance to the next file to be written according to the FT
			tempEntry = tempEntry->nextFTEntry;
			j++;
		}
		return (outfile+".gkv").c_str();
	}

    return "";
}

std::string // returns the name of the file created
GKV_File::createZ(std::string dirpath) // Compresses each file.
{
	boost::regex e("^(.*/)?(?:$|(.+?)(?:(\\.[^.]*$)|$))",boost::regex::icase|boost::regex::perl);
	// File pointer for writing to the GKV
	FILE*	GKVStream;
	// Output filename derived from working_path
	std::string working_path;
	std::string outfile;
	dword_t counter = 0;

	std::list<filelist_t> rootList;

	working_path = generateFileList(dirpath, rootList, dirpath.length());
	working_path = boost::regex_replace(working_path,e,"$0");
	outfile = boost::regex_replace(working_path,e,"$2"); // DOES NOT INCLUDE FILE EXTENSION!!!

	// Get the file count:
	std::list<filelist_t>::iterator it;
	for(it = rootList.begin(); it != rootList.end(); it++)
	{
		std::vector<std::string>::iterator its;
		for(its = (*it).theList.begin(); its != (*it).theList.end(); its++)
		{
			counter++;
		}
	}

	if (rootList.size() > 0)
	{
		// Generate the Header and File Tables
		// (File List, Working Path, Counter, Compression)
		if (!generateHFT(rootList, working_path, counter, true))
		{
			return "";
		}
		rootList.clear();

		// Work out the offsets
		if (!workOutOffsets())
		{
			return "";
		}


		std::cout << "------------" << std::endl;
		std::cout << "Working Path: " << working_path.c_str() << std::endl;
		std::cout << "------------" << std::endl;

		// Open the file stream for writing the GKV
		GKVStream = fopen((outfile+".gkv").c_str(), "wb");
		if( !GKVStream ){ return ""; }

		// Write the GKV header
		fwrite( &m_Header, sizeof(header_t), 1, GKVStream );

		// Process the File Table
		// Get a local copy of the file table head
		tableentry_t* tempEntry;
		tempEntry = m_FileTable;

		// Write out each file table entry
		while( tempEntry != 0 )
		{
			// Check for the dummy entry (break if found)
			if( tempEntry->nextFTEntry == 0 ){ break; }

			// Create a BYTE array for byte access to a file table entry
			byte_t Ptr[sizeof(tableentry_t)] = {0};

			// Copy the current file table entry in to the byte array
			memcpy( &Ptr, tempEntry, sizeof(tableentry_t) );

			for( unsigned int i = 0; i < sizeof(tableentry_t); i++ )
			{
				// Temporary BYTE variable
				byte_t Temp = 0;

				// Make equal to the relevant byte of the FT entry
				Temp = Ptr[i];

				////
				// Base64 Encoding goes here (posibly???)
				////

				// Write the FT BYTE value
				fwrite( &Temp, sizeof(byte_t), 1, GKVStream );
			}

			// Move on to the next file table entry
			tempEntry = tempEntry->nextFTEntry;
		}

		// Close the stream used for the header and file table
		fclose(GKVStream);


//// TESTING



		// Reset variable to be the head of the file table
		tempEntry = m_FileTable;

		std::cout << "----------" << std::endl;
		std::cout << "Processing" << std::endl;
		std::cout << "----------" << std::endl;
		// Read in each file at a time, encrypt it using the cypher and add to GKV
		int j = 1;
		while( tempEntry != 0 )
		{
			boost::filesystem::path pathout = ".";
			std::string outpath = boost::filesystem::system_complete(pathout).string();

			// Check for the dummy file table entry
			if( tempEntry->nextFTEntry == 0 )
			{
				return (outfile+".gkv").c_str();
			}

			char Buffer[300] = {0};
			FILE* InputStream;
			byte_t* dataBuffer = (byte_t*)malloc(tempEntry->fileSize); // All purpose data buffer
			dword_t destLen = compressBound(tempEntry->fileSize);
			byte_t* destBuffer = (byte_t*)malloc(destLen);

			// Display which guid is being processed
			std::cout << "(" << j << "/" << m_Header.numFTEntries << ") | " << tempEntry->guid;

			// Open the GKV file for writing
			GKVStream = fopen( (outfile+".gkv").c_str(), "r+b" );
			if( !GKVStream ){ return ""; }

			// Create the absolute path of the file to be added to the GKV
			memcpy( Buffer, (working_path+"/"+tempEntry->guid).c_str(), 300 );



			// Open the data stream for reading in a file to be added to the GVK
			// NEED TO COMPRESS THIS DATA BEFORE WRITING IT TO THE GKV FILE!!!
			InputStream = fopen( Buffer, "rb" );
			if( !InputStream )
			{
				free(dataBuffer);
				free(destBuffer);

				fclose( GKVStream );
				return "";
			}
			int k = 0;
			for (dword_t x = 0; x < tempEntry->fileSize; x++)
			{
				if (k == 128000)
				{
					std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ") / " << tempEntry->guid;
					k++;
				}
				else if (k == 256000)
				{
					std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ") - " << tempEntry->guid;
					k++;
				}
				else if (k == 384000)
				{
					std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ") \\ " << tempEntry->guid;
					k++;
				}
				else if (k == 512000)
				{
					std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ") | " << tempEntry->guid;
					k = 0;
				} else {
					k++;
				}

				dataBuffer[x] = fgetc(InputStream); // Fill the buffer
			}
			std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ") Compressing... " << tempEntry->guid;
			int z_result = compress(destBuffer, &destLen, (const byte_t*)dataBuffer, tempEntry->fileSize);
			fclose(InputStream);
			switch( z_result )
			{
			case Z_OK:
				//printf("***** SUCCESS! *****\n");
				break;

			case Z_MEM_ERROR:
				printf("\n=Out of memory=\n");
				exit(1);    // quit.
				break;

			case Z_BUF_ERROR:
				printf("\n=Output buffer wasn't large enough=\n");
				exit(1);    // quit.
				break;
			case Z_DATA_ERROR:
				printf("\n=DATA ERROR=\n");
				exit(1);
				break;
			}


			// Set the position in the GKV file for this file to be written at
			fpos_t pos;
			pos = tempEntry->offset;
			fsetpos( GKVStream, &pos );

			for (dword_t n = 0; n < destLen; n++)
			{
				// Get the first BYTE from the file to be added
				// Temporary BYTE variable
				byte_t Temp = 0;

				// Make equal to the relevant byte of the FT entry
				Temp = destBuffer[n];

				////
				// Base64 Encoding goes here (posibly???)
				////

				// Write the FT BYTE value

				fwrite( &Temp, sizeof(byte_t), 1, GKVStream );
			}
			//fwrite( &destBuffer, destLen, 1, GKVStream );

			std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ")   " << tempEntry->guid;
			std::cout << std::endl;

			// Free the buffers
			free(dataBuffer);
			free(destBuffer);
			// Close both the file streams
			fclose( InputStream );
			fclose( GKVStream );

			// Advance to the next file to be written according to the FT
			tempEntry = tempEntry->nextFTEntry;
			j++;
		}
		return (outfile+".gkv").c_str();

	}

    return "";
}

bool
GKV_File::extractAll(std::string gkvfile)
{
	boost::regex e("^(.*/)?(?:$|(.+?)(?:(\\.[^.]*$)|$))",boost::regex::icase|boost::regex::perl);
	std::string outname = boost::regex_replace(gkvfile,e,"$2"); // DOES NOT INCLUDE FILE EXTENSION!!!
	std::string ext = boost::regex_replace(gkvfile,e,"$3");

	boost::filesystem::path pathout;
	std::string outpath;

	pathout = ("./"+outname).c_str();

	if (ext != ".gkv")
	{
		std::cout << "File does not have the '.gkv' file extension. Aborted!" << std::endl;
		return false;
	}
	if (boost::filesystem::exists(pathout))
	{
		std::cout << "Directory '" << outname << "' exists." << std::endl;
		std::cout << "The directory created will be the same name as the file." << std::endl;
		std::cout << "This program will not over-write an existing directory." << std::endl;
		std::cout << "It is up to the user to delete or rename '" << outname << "'. Aborted!" << std::endl;
		return false;
	}
	// Process input file, fetch header and file table information
	if(!fetchHeader(gkvfile))
	{
		std::cout << "Couldn't read header information. Aborted!" << std::endl;
		return false;
	}
	if(!fetchFileTable(gkvfile))
	{
		std::cout << "Couldn't read file table information. Aborted!" << std::endl;
		return false;
	}

	// Create output directories and files
	std::cout << "------------------" << std::endl;
	std::cout << "Extracting Package" << std::endl;
	std::cout << "------------------" << std::endl;

	tableentry_t* tempEntry;
	tempEntry = m_FileTable;

	if (!m_Header.isCompressed)
	{
		int j = 1;
		while( tempEntry != 0 )
		{
			pathout = ("./"+outname+"/"+tempEntry->guid).c_str();
			outpath = boost::filesystem::system_complete(pathout).string();

			if( tempEntry->nextFTEntry == 0 )
			{
				return true;
			}

			// Declare local variables
			FILE* GKVStream;
			FILE* OutputStream;
			fpos_t pos;
			byte_t dataBuffer = 0;

			std::cout << "(" << j << "/" << m_Header.numFTEntries << ")   " << tempEntry->guid;

			// Create destination directory if it doesn't exists.
			if (!boost::filesystem::exists(pathout.parent_path()))
			{
				boost::filesystem::create_directories(pathout.parent_path());
			}

			// Process file
			pos = tempEntry->offset;
			GKVStream = fopen(gkvfile.c_str(), "rb");
			fsetpos(GKVStream, &pos);

			if (!GKVStream)
			{
				std::cout << "Error openening '.gkv' file. Aborted!" << std::endl;
				return false;
			} else {
				OutputStream = fopen(outpath.c_str(), "wb");
				if (!OutputStream)
				{
					std::cout << "Error creating '" << outpath << "'. Aborted!" << std::endl;
				}

				// Read in the file a byte at a time, write to OutStream
				int k = 0;
				for( dword_t i = 0; i < tempEntry->fileSize; i++ )
				{
					if (k == 128000)
					{
						std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ") / " << tempEntry->guid;
						k++;
					}
					else if (k == 256000)
					{
						std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ") - " << tempEntry->guid;
						k++;
					}
					else if (k == 384000)
					{
						std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ") \\ " << tempEntry->guid;
						k++;
					}
					else if (k == 512000)
					{
						std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ") | " << tempEntry->guid;
						k = 0;
					} else {
						k++;
					}


					// Get the first BYTE from the file to be added
					dataBuffer = fgetc( GKVStream );

					////
					// Encrypt the file accordingly
					////

					// Write to the GKV file starting at the offset
					fwrite( &dataBuffer, sizeof(byte_t), 1, OutputStream );
				}
			}
			// Close both the file streams
			fclose( GKVStream );
			fclose( OutputStream );

			std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ")   " << tempEntry->guid;
			std::cout << std::endl;

			tempEntry = tempEntry->nextFTEntry;
			j++;
		}
	}
	else // Compression was used
	{
		int j = 1;
		while( tempEntry != 0 )
		{
			pathout = ("./"+outname+"/"+tempEntry->guid).c_str();
			outpath = boost::filesystem::system_complete(pathout).string();

			if( tempEntry->nextFTEntry == 0 )
			{
				return true;
			}

			// Declare local variables
			FILE* GKVStream;
			FILE* OutputStream;
			fpos_t pos;


			std::cout << "(" << j << "/" << m_Header.numFTEntries << ")   " << tempEntry->guid;

			// Create destination directory if it doesn't exists.
			if (!boost::filesystem::exists(pathout.parent_path()))
			{
				boost::filesystem::create_directories(pathout.parent_path());
			}

			// Process file
			pos = tempEntry->offset;
			GKVStream = fopen(gkvfile.c_str(), "rb");
			fsetpos(GKVStream, &pos);

			if (!GKVStream)
			{
				std::cout << "Error openening '.gkv' file. Aborted!" << std::endl;
				return false;
			} else {
				OutputStream = fopen(outpath.c_str(), "wb");
				if (!OutputStream)
				{
					std::cout << "Error creating '" << outpath << "'. Aborted!" << std::endl;
				}
				// Compression variables
				dword_t destLen = tempEntry->fileSize;
				byte_t* destBuffer = (byte_t*)malloc(destLen);
				dword_t sourceLen = tempEntry->compSize;
				byte_t* sourceBuffer = (byte_t*)malloc(sourceLen);

				int k = 0;
				for (dword_t x = 0; x < sourceLen; x++)
				{
					if (k == 128000)
					{
						std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ") / " << tempEntry->guid;
						k++;
					}
					else if (k == 256000)
					{
						std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ") - " << tempEntry->guid;
						k++;
					}
					else if (k == 384000)
					{
						std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ") \\ " << tempEntry->guid;
						k++;
					}
					else if (k == 512000)
					{
						std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ") | " << tempEntry->guid;
						k = 0;
					} else {
						k++;
					}

					sourceBuffer[x] = fgetc(GKVStream);
				}
				std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ") Decompressing... " << tempEntry->guid;
				int z_result = uncompress(destBuffer, &destLen, (const byte_t*)sourceBuffer, sourceLen);
				switch( z_result )
				{
				case Z_OK:
					//printf("***** SUCCESS! *****\n");
					break;

				case Z_MEM_ERROR:
					printf("\n=Out of memory=\n");
					exit(1);    // quit.
					break;

				case Z_BUF_ERROR:
					printf("\n=Output buffer wasn't large enough=\n");
					exit(1);    // quit.
					break;
				case Z_DATA_ERROR:
					printf("\n=DATA ERROR=\n");
					exit(1);
					break;
				}

				////
				// Encrypt the file accordingly
				////

				// Write to the GKV file starting at the offset
				for (dword_t x = 0; x < tempEntry->fileSize; x++)
				{
					fputc(destBuffer[x], OutputStream);
				}

				// Free the buffers
				free(sourceBuffer);
				free(destBuffer);
			}


			// Close both the file streams
			fclose( GKVStream );
			fclose( OutputStream );

			std::cout << "\r" << "(" << j << "/" << m_Header.numFTEntries << ")   " << tempEntry->guid;
			std::cout << std::endl;

			tempEntry = tempEntry->nextFTEntry;
			j++;
		}
	}

	return true;
}

/* Not implemented yet.
SDL_RWops*
GKV_File::SDL_RWFromGKV(const char* gkvfile, const char* path_to_file, const char* mode)
{
		//Create an SDL_RWops* from the buffer containing the filename

	return NULL;
}
*/


// Private
bool
GKV_File::generateHFT(std::list<filelist_t> &rootList, std::string working_path, dword_t &fileCount, bool compressed)
{
	m_Header.numFTEntries = fileCount;
	std::cout << "----------" << std::endl;
	std::cout << "File Count: " << m_Header.numFTEntries << std::endl;
	std::cout << "----------" << std::endl;

	m_Header.isCompressed = compressed;
	if (m_Header.isCompressed)
	{
		memcpy(m_Header.gkvSignature, "GKV01DZC", 8);
		std::cout << "----------------" << std::endl;
		std::cout << "Header Signature: " << getEscapedValue(m_Header.gkvSignature, 8) << std::endl;
		std::cout << "----------------" << std::endl;

		std::cout << "-----------------" << std::endl;
		std::cout << "Using Compression: yes" << std::endl;
		std::cout << "-----------------" << std::endl;
	} else {
		memcpy(m_Header.gkvSignature, "GKV01DNC", 8);
		std::cout << "----------------" << std::endl;
		std::cout << "Header Signature: " << getEscapedValue(m_Header.gkvSignature, 8) << std::endl;
		std::cout << "----------------" << std::endl;

		std::cout << "-----------------" << std::endl;
		std::cout << "Using Compression: no" << std::endl;
		std::cout << "-----------------" << std::endl;
	}

	////
	// LOOPING THROUGH LIST!!!
	// Create a node entry for the file table
	//tableentry_t* tempEntry = new tableentry_t();
	// Store the file name aka "guid"
	//memcpy(tempEntry->guid, path+theList[item], 256);
	// Store the file size
	//tempEntry->fileSize = fs::file_size(p);
	// Default the offset value (worked out later)
	//tempEntry->offset = 0;
	// Add this to the file table
	//tempEntry->nextFTEntry = m_FileTable;
	//m_FileTable = tempEntry;
	// END LOOPING!!!
	////

	std::cout << "-------------------" << std::endl;
	std::cout << "Creating File Table" << std::endl;
	std::cout << "-------------------" << std::endl;

	std::list<filelist_t>::iterator it;
	for(it = rootList.begin(); it != rootList.end(); it++)
	{
		std::vector<std::string>::iterator its;
		for(its = (*it).theList.begin(); its != (*it).theList.end(); its++)
		{
			std::cout << "+";
			// Set the fully qualified path to file
			boost::filesystem::path fspath;
			// Create a node entry for the file table
			tableentry_t* tempEntry = new tableentry_t();
			// Generate Output
			if ((*it).path == "")
			{
				// Store the file name aka "guid"
				memcpy(tempEntry->guid, (*its).c_str(), (*its).length());
				fspath = (working_path+"/"+(*its)).c_str();
			}
			else
			{
				// Store the file name aka "guid"
				memcpy(tempEntry->guid, ((*it).path+"/"+(*its)).c_str(), ((*it).path+"/"+(*its)).length());
				fspath = (working_path+"/"+(*it).path+"/"+(*its)).c_str();
			}
			// Store the file size
			tempEntry->fileSize = boost::filesystem::file_size(fspath);

			////
			// Default the compSize and offset values (worked out later)
			tempEntry->compSize = 0;
			tempEntry->offset = 0;

			if (m_Header.isCompressed)
			{
				// Get the Compressed Filesize
				FILE* InputStream;
				byte_t* dataBuffer = (byte_t*)malloc(tempEntry->fileSize); // All purpose data buffer
				dword_t destLen = compressBound(tempEntry->fileSize);
				byte_t* destBuffer = (byte_t*)malloc(destLen);

				InputStream = fopen((fspath.string()).c_str(), "rb");
				if (!InputStream)
				{
					std::cout << std::endl << "Error opening '" << fspath.string() << "' Abort!" << std::endl;
					free(dataBuffer);
					free(destBuffer);
					return false;
				} else {
					for (dword_t x = 0; x < tempEntry->fileSize; x++)
					{
						dataBuffer[x] = fgetc(InputStream); // Fill the buffer
					}
					compress(destBuffer, &destLen, (const byte_t*)dataBuffer, tempEntry->fileSize);
					tempEntry->compSize = destLen; // Compressed filesize found!

					fclose(InputStream);
				}
				free(dataBuffer);
				free(destBuffer);
			}

			// Add this to the file table
			tempEntry->nextFTEntry = m_FileTable;
			m_FileTable = tempEntry;
		}
	}
	std::cout << std::endl;


	return true;
}

bool
GKV_File::workOutOffsets(void)
{
	std::cout << "-----------------" << std::endl;
	std::cout << "Adjusting Offsets" << std::endl;
	std::cout << "-----------------" << std::endl;

	// Declare local variables
	dword_t	dwFileHFTData = 0;			// Size of header and file table
	dword_t	dwOffset = 0;				// Individual files offset in to the GKV

	// Work out the size, in bytes, of the header and FT
	dwFileHFTData = sizeof(header_t) + (m_Header.numFTEntries * sizeof(tableentry_t));

	// Create a temporary node and make it the head of the linked list
	tableentry_t* tempEntry;
	tempEntry = m_FileTable;

	// Get the first offset
	dwOffset = dwFileHFTData + 1;

	while( tempEntry != 0 )
	{
		// Check for the dummy entry
		if( tempEntry->nextFTEntry == 0 )
		{
			std::cout << std::endl;
			return true;
		}

		std::cout << "+";

		// Set the offset
		tempEntry->offset = dwOffset;

		if (m_Header.isCompressed)
		{
			// Update the offset
			dwOffset = dwOffset + tempEntry->compSize;
		}
		else
		{
			// Update the offset
			dwOffset = dwOffset + tempEntry->fileSize;
		}

		// Update the current variable
		tempEntry = tempEntry->nextFTEntry;
	}
	std::cout << std::endl;

	return true;
}

bool
GKV_File::fetchHeader(std::string gkvfile)
{
	FILE * pFile;
	fpos_t pos = 0; // File position
	byte_t dataBuffer[9] = {0}; // DATA buffer
	memset(dataBuffer, 0, sizeof(dataBuffer)); // Be sure to clear buffer after each use!

	// Set dummy values
	memcpy(m_Header.gkvSignature, "GKVDUMMY", 8);
	m_Header.numFTEntries = 0;

	pFile = fopen (gkvfile.c_str(), "r");
	if (pFile == 0)
	{
		perror ("Error opening file.");
		return false;
	}
	else
	{
		fgets((char*)dataBuffer, 9, pFile);
		memcpy(m_Header.gkvSignature, dataBuffer, 8);
		memset(dataBuffer, 0, sizeof(dataBuffer)); // Clear buffer after each use!
		fclose (pFile);
	}

	pFile = fopen(gkvfile.c_str(), "rb");
	if (pFile == 0)
	{
		perror ("Error opening file");
		return false;
	}
	else
	{
		// Retrieve Number of Entries
		dword_t ftEntries = 0;
		pos = 8;

		fsetpos(pFile, &pos);
		dataBuffer[0] = fgetc(pFile);
		dataBuffer[1] = fgetc(pFile);
		dataBuffer[2] = fgetc(pFile);
		dataBuffer[3] = fgetc(pFile);

		ftEntries = dataBuffer[0] |
					(dataBuffer[1] << 8)  |
					(dataBuffer[2] << 16) |
					(dataBuffer[3] << 24);
		m_Header.numFTEntries = ftEntries;

		for(unsigned int x = 0; x < sizeof(dword_t); x++)
		{
			dataBuffer[x] = 0; // Clear buffer after each use!
		}
		// Retrieve Compression boolean
		dword_t comp = 0;
		pos = 12;

		fsetpos(pFile, &pos);
		dataBuffer[0] = fgetc(pFile);
		dataBuffer[1] = fgetc(pFile);
		dataBuffer[2] = fgetc(pFile);
		dataBuffer[3] = fgetc(pFile);

		comp = dataBuffer[0] |
					(dataBuffer[1] << 8)  |
					(dataBuffer[2] << 16) |
					(dataBuffer[3] << 24);
		m_Header.isCompressed = comp;

		fclose (pFile);
	}
#ifndef GKV_SILENT
std::cout << "-------------" << std::endl;
std::cout << "GKV Signature: " << getEscapedValue(m_Header.gkvSignature, 8) << std::endl;
std::cout << "-------------" << std::endl;
std::cout << "   File Count: " << m_Header.numFTEntries << std::endl;
std::cout << "-------------" << std::endl;
if (m_Header.isCompressed)
{
std::cout << "  Compression: yes" << std::endl;
} else {
std::cout << "  Compression: no" << std::endl;
}
std::cout << "-------------" << std::endl;
#endif

	return true;
}

bool
GKV_File::fetchFileTable(std::string gkvfile)
{
	// Declare local variables
	dword_t	dwOffset = sizeof(header_t);				// Individual files offset table info
	tableentry_t* tempEntry = 0;

	FILE * pFile;
	fpos_t pos = 0; // File position
	byte_t dataBuffer[256] = {0}; // DATA buffer
	memset(dataBuffer, 0, sizeof(dataBuffer)); // Be sure to clear buffer after each use!

	pFile = fopen (gkvfile.c_str(), "rb");
	if (pFile == 0)
	{
		perror ("Error opening '.gkv' file. Aborted!");
		return false;
	}
	else
	{
#ifndef GKV_SILENT
std::cout << "-------------------" << std::endl;
std::cout << "Fetching File Table" << std::endl;
std::cout << "-------------------" << std::endl;
#endif
		for (unsigned int x = 0; x < m_Header.numFTEntries; x++)
		{
#ifndef GKV_SILENT
std::cout << "+";
#endif
			// Create a node entry for the file table
			tempEntry = new tableentry_t();

			// Store the file name aka "guid"
			pos = dwOffset;
			fsetpos(pFile, &pos);
			fgets((char*)dataBuffer, sizeof(dataBuffer), pFile);
			memcpy(tempEntry->guid, dataBuffer, sizeof(dataBuffer));
			memset(dataBuffer, 0, sizeof(dataBuffer)); // Clear buffer after each use!

			// Store the file size
			pos = dwOffset + sizeof(tempEntry->guid);
			fsetpos(pFile, &pos);

			dataBuffer[0] = fgetc(pFile);
			dataBuffer[1] = fgetc(pFile);
			dataBuffer[2] = fgetc(pFile);
			dataBuffer[3] = fgetc(pFile);

			dword_t fsize = dataBuffer[0] |
							(dataBuffer[1] << 8) |
							(dataBuffer[2] << 16) |
							(dataBuffer[3] << 24);

			tempEntry->fileSize = fsize;
			memset(dataBuffer, 0, sizeof(dataBuffer)); // Clear buffer after each use!

			// Store the compressed filesize values
			pos = dwOffset + sizeof(tempEntry->guid) + sizeof(dword_t);
			fsetpos(pFile, &pos);

			dataBuffer[0] = fgetc(pFile);
			dataBuffer[1] = fgetc(pFile);
			dataBuffer[2] = fgetc(pFile);
			dataBuffer[3] = fgetc(pFile);

			dword_t fcompsize = dataBuffer[0] |
								(dataBuffer[1] << 8) |
								(dataBuffer[2] << 16) |
								(dataBuffer[3] << 24);

			tempEntry->compSize = fcompsize;
			memset(dataBuffer, 0, sizeof(dataBuffer)); // Clear buffer after each use!

			// Store the offset values
			pos = dwOffset + sizeof(tempEntry->guid) + sizeof(dword_t) + sizeof(dword_t);
			fsetpos(pFile, &pos);

			dataBuffer[0] = fgetc(pFile);
			dataBuffer[1] = fgetc(pFile);
			dataBuffer[2] = fgetc(pFile);
			dataBuffer[3] = fgetc(pFile);

			dword_t foffset = dataBuffer[0] |
							(dataBuffer[1] << 8) |
							(dataBuffer[2] << 16) |
							(dataBuffer[3] << 24);

			tempEntry->offset = foffset;
			memset(dataBuffer, 0, sizeof(dataBuffer)); // Clear buffer after each use!

// DEBUG:
//std::cout << std::endl << "GUID: " << tempEntry->guid << std::endl;
//std::cout << "File Size: " << tempEntry->fileSize << std::endl;
//std::cout << "Comp Size: " << tempEntry->compSize << std::endl;
//std::cout << "Offset: " << tempEntry->offset << std::endl;

			// Add this to the file table
			tempEntry->nextFTEntry = m_FileTable;
			m_FileTable = tempEntry;

			// Position the offset for the next entry
			dwOffset = dwOffset + sizeof(tableentry_t);

		}
		fclose (pFile);
	}
#ifndef GKV_SILENT
std::cout << std::endl;
#endif

	return true;
}

byte_t*
GKV_File::getFileData(std::string gkvfile, std::string guid, int* size)
{
	boost::regex e("^(.*/)?(?:$|(.+?)(?:(\\.[^.]*$)|$))",boost::regex::icase|boost::regex::perl);
	std::string outname = boost::regex_replace(gkvfile,e,"$2"); // DOES NOT INCLUDE FILE EXTENSION!!!
	std::string ext = boost::regex_replace(gkvfile,e,"$3");

	if (ext != ".gkv")
	{
		std::cout << "File does not have the '.gkv' file extension. Aborted!" << std::endl;
		return false;
	}

	// Process input file, fetch header and file table information
	if(!fetchHeader(gkvfile))
	{
		std::cout << "Couldn't read header information. Aborted!" << std::endl;
		return false;
	}
	if(!fetchFileTable(gkvfile))
	{
		std::cout << "Couldn't read file table information. Aborted!" << std::endl;
		return false;
	}

	tableentry_t* tempEntry;
	tempEntry = m_FileTable;

	if (!m_Header.isCompressed)
	{
		int j = 1;
		while( tempEntry != 0 )
		{
			if( tempEntry->nextFTEntry == 0 )
			{
				*size = 0;
				return (byte_t*)"";
			}

			if (static_cast<std::string>(tempEntry->guid) == guid)
			{
				// Declare local variables
				FILE* GKVStream;
				fpos_t pos;
				byte_t* dataBuffer = (byte_t*)malloc(tempEntry->fileSize);

				// Process file
				pos = tempEntry->offset;
				GKVStream = fopen(gkvfile.c_str(), "rb");
				fsetpos(GKVStream, &pos);

				if (!GKVStream)
				{
					std::cout << "Error openening '.gkv' file. Aborted!" << std::endl;
					return false;
				} else {
					// Read in the file a byte at a time, write to OutStream
					for( dword_t i = 0; i < tempEntry->fileSize; i++ )
					{
						// Get the first BYTE from the file to be added
						dataBuffer[i] = fgetc( GKVStream );
					}
				}
				// Close both the file streams
				fclose( GKVStream );

				*size = (int)tempEntry->fileSize;
				return dataBuffer;
			}
			tempEntry = tempEntry->nextFTEntry;
			j++;
		}
	}
	else // Compression was used
	{
		int j = 1;
		while( tempEntry != 0 )
		{
			if( tempEntry->nextFTEntry == 0 )
			{
				*size = 0;
				return (byte_t*)"";
			}
			if (static_cast<std::string>(tempEntry->guid) == guid)
			{
				// Declare local variables
				FILE* GKVStream;
				fpos_t pos;

				// Process file
				pos = tempEntry->offset;
				GKVStream = fopen(gkvfile.c_str(), "rb");
				fsetpos(GKVStream, &pos);

				// Compression variables
				dword_t destLen = tempEntry->fileSize;
				byte_t* destBuffer = (byte_t*)malloc(destLen);
				dword_t sourceLen = tempEntry->compSize;
				byte_t* sourceBuffer = (byte_t*)malloc(sourceLen);

				if (!GKVStream)
				{
					std::cout << "Error openening '.gkv' file. Aborted!" << std::endl;
					return false;
				} else {
					for (dword_t x = 0; x < sourceLen; x++)
					{
						sourceBuffer[x] = fgetc(GKVStream);
					}
					int z_result = uncompress(destBuffer, &destLen, (const byte_t*)sourceBuffer, sourceLen);
					switch( z_result )
					{
					case Z_OK:
						//printf("***** SUCCESS! *****\n");
						break;

					case Z_MEM_ERROR:
						printf("\n=Out of memory=\n");
						exit(1);    // quit.
						break;

					case Z_BUF_ERROR:
						printf("\n=Output buffer wasn't large enough=\n");
						exit(1);    // quit.
						break;
					case Z_DATA_ERROR:
						printf("\n=DATA ERROR=\n");
						exit(1);
						break;
					}

					// Free the source buffer
					free(sourceBuffer);

				}
				// Close both the file streams
				fclose( GKVStream );

				*size = (int)tempEntry->fileSize;
				return destBuffer;

			}
			tempEntry = tempEntry->nextFTEntry;
			j++;
		}
	}

	*size = 0;
	return (byte_t*)"";
}

} // namespace
